package titt.service.content;

import java.io.File;
import java.io.OutputStream;

import titt.TITTValues;
import titt.head.HTTPHead;
import titt.head.HeadFactory;
import titt.head.Header;
import titt.head.request.MethodHead;
import titt.head.request.RequestHead;
import titt.head.request.form.FormHead;
import titt.head.FileHead;
import titt.io.IOFactory;
import titt.io.socket.TITTSocket;
import titt.io.socket.in.ListenerStream;
import titt.io.socket.in.TITTInputStream;
import titt.io.socket.out.LengthBufferedOutputStream;
import titt.io.socket.out.TITTOutputStream;
import titt.io.util.wrap.AutoWrapper;
import titt.io.writer.head.HeadWriter;
import titt.util.io.pipe.PipeListener;

public class NuderRequest implements NuderContent {

	private boolean complete = false;
	
	private TITTInputStream contentStream;
	private Object content;
	
	private int length;
	private final HTTPHead meta;

	private PipeListener listener;
	
	private FormHead fh;
	
	public NuderRequest(Object o, PipeListener... listeners) {
		this.listener = IOFactory.createInternalListener(listeners);
		this.content = o;
		
		String mime = AutoWrapper.get().getMimeType(o), extra = "";
		
		this.fh = new FormHead(0, HeadFactory.generateBoundary(), HeadFactory.EMPTY);
		
		if(o instanceof File)
			extra = "; filename=\""+((File)o).getName()+"\"";
		
		this.meta = HeadFactory.createHead(new Header("Content-Disposition", "form-data; name=\"nuder\"" + extra), new Header("Content-Type", mime));

		LengthBufferedOutputStream tmp = new LengthBufferedOutputStream();

		try {
			tmp.writeLine(fh.getBounds()[0]);
			IOFactory.createHeadWriter(tmp).writeHead(meta);
			tmp.writeLine();
			tmp.writeLine(fh.getBounds()[1]);
			
		}
		catch(Exception e) {
		}
		
		this.fh = new FormHead(AutoWrapper.get().getLength(o) + tmp.getLength(), fh.getBoundary(), HeadFactory.EMPTY);
		this.length = fh.getContentLength();		
	}
	
	NuderRequest(RequestHead head, TITTInputStream in) throws Exception {
		
		this.fh = new FormHead(head);
		int headLength = fh.getBounds()[0].length + TITTValues.HTTP_NEWLINE_LENGTH;
		in.read(new byte[headLength]);

		this.meta = IOFactory.createHeadReader(in).readHead(false);
		
		int tailLength = fh.getBounds()[1].length + 2 * TITTValues.HTTP_NEWLINE_LENGTH;
		
		this.length = fh.getContentLength() - headLength - tailLength - NuderUtil.getHeadLength(meta);

		this.contentStream = IOFactory.createInputStream(new ListenerStream(length, 0, tailLength, null, in.asStream()));	
	}

	
	
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getContentObject() {
		return (T)content;
	}

	@Override
	public TITTInputStream getContentStream() {
		return contentStream;
	}

	@Override
	public int getLength() {
		return length;
	}
	
	public FormHead getFormHead() {
		return fh;
	}
	
	public FileHead getContentHead() {
		return new FileHead(meta);
	}

	@Override
	public synchronized void waitForCompletion() {
		while(!complete)
			try {
				wait();
			}
			catch(Exception e) {}
	}

	@Override
	public void writeToStream(OutputStream out, PipeListener... listeners) throws Exception {
		writeToStream(IOFactory.createOutputStream(out), listeners);
	}

	@Override
	public void writeToStream(TITTOutputStream out, PipeListener... listeners) throws Exception {
		writeToStreamInternal(out, listeners);
		triggerComplete();
	}
	
	void encodeAndWriteToSocket(TITTSocket s, MethodHead proto) throws Exception {
		fh = new FormHead(fh.getContentLength(), fh.getBoundary(), proto);
			
		TITTOutputStream out = s.getOutputStream();
		HeadWriter hw = s.getHeadWriter();
			
		hw.writeHead(fh);
		out.writeLine(fh.getBounds()[0]);
		out.flush();
		
		hw.writeHead(meta);
		writeToStreamInternal(out, listener);

		out.writeLine();
		out.writeLine(fh.getBounds()[1]);
		out.flush();

		triggerComplete();
	}
	
	private void writeToStreamInternal(final TITTOutputStream out, PipeListener... listeners) throws Exception {
		PipeListener pl = IOFactory.createInternalNuderListener(out, contentStream, listeners);
		
		if(contentStream == null)
			AutoWrapper.get().writeTo(pl, out, content);
		else
			try {
				IOFactory.createStreamPiper().pipe(contentStream, out, length, pl);
			}
			catch(Exception e) {
				contentStream.close();
				throw e;
			}
		
		out.flush();
	}
	
	private synchronized void triggerComplete() {
		complete = true;
		notifyAll();
	}
	
}
